package service;

import annotation.DependencyInjection;
import annotation.Singleton;
import entity.Relation;
import enums.RelationState;
import enums.ResponseErrorCode;
import exception.ServerException;
import repository.RelationRepository;
import response.relation.*;

import java.time.LocalDateTime;
import java.util.List;

@Singleton
public class RelationService {
    private RelationRepository relationRepository;

    @DependencyInjection
    public RelationService(RelationRepository relationRepository) {
        this.relationRepository = relationRepository;
    }

    public FriendListResponse getAllFriendUsernames(String username) {
        relationRepository.startRead();
        try {
            List<String> usernameList = relationRepository.getAllFriendUsernames(username);
            return new FriendListResponse(usernameList);
        } finally {
            relationRepository.endRead();
        }
    }

    public RelationStateResponse getRelationState(String hostUsername, String guestUsername) {
        relationRepository.startRead();
        try {
            Relation relation = relationRepository.get(hostUsername, guestUsername);
            if (relation == null) {
                return new RelationStateResponse(RelationState.Unrelated);
            }
            return new RelationStateResponse(relation.getState());
        } finally {
            relationRepository.endRead();
        }
    }

    public FriendAddResponse addFriendRequest(String hostUsername, String guestUsername) {
        relationRepository.startWrite();
        try {
            Relation relation = relationRepository.get(hostUsername, guestUsername);
            if (relation != null) {
                if (RelationState.Friend.equals(relation.getState())) {
                    throw new ServerException(ResponseErrorCode.ALREADY_FRIENDS);
                } else if (RelationState.Rejected.equals(relation.getState())) {
                    throw new ServerException(ResponseErrorCode.ALREADY_REJECTED);
                }
            }
            changeState(hostUsername, guestUsername, RelationState.Applying);
            return new FriendAddResponse(true);
        } finally {
            relationRepository.endWrite();
        }
    }

    public FriendPullApplicationResponse getFriendRequests(String username) {
        relationRepository.startRead();
        try {
            List<String> usernameList = relationRepository.getAllApplyingUsernames(username);
            return new FriendPullApplicationResponse(usernameList);
        } finally {
            relationRepository.endRead();
        }
    }

    public FriendAddDecisionResponse dealAddRequest(String hostUsername, String guestUsername, boolean accept) {
        relationRepository.startWrite();
        try {
            Relation relation = relationRepository.get(guestUsername, hostUsername);
            if (relation == null || !relation.getState().equals(RelationState.Applying)) {
                throw new ServerException(ResponseErrorCode.FRIEND_REQUEST_EXPIRE);
            }

            if (accept) {
                LocalDateTime now = LocalDateTime.now();
                changeState(hostUsername, guestUsername, RelationState.Friend);
                changeState(guestUsername, hostUsername, RelationState.Friend);
                relationRepository.updateTimestamp(hostUsername, guestUsername, now.minusNanos(1));
                relationRepository.updateTimestamp(guestUsername, hostUsername, now.minusNanos(1));
            } else {
                changeState(hostUsername, guestUsername, RelationState.Rejecting);
                changeState(guestUsername, hostUsername, RelationState.Rejected);
            }

            return new FriendAddDecisionResponse(true);
        } finally {
            relationRepository.endWrite();
        }
    }

    private void changeState(String hostUsername, String guestUsername, RelationState state) { // require relation repository lock
        if (relationRepository.updateState(hostUsername, guestUsername, state)) {
            return;
        }

        relationRepository.add(new Relation(hostUsername, guestUsername, state, LocalDateTime.now().minusNanos(1)));
    }
}
